package com.shadow.demo.dictionary.common;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.shadow.demo.dictionary.common.data.ContextHolder;
import com.shadow.demo.dictionary.common.data.Priority;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.core.env.Environment;

import java.io.Closeable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * Spring Bean辅助工具类
 *
 * @author penggch
 */
@Slf4j
public class BeanUtil {

    private static final ContextHolder globalContextHolder = new ContextHolder();
    private static ApplicationContext applicationContext;
    private static final Map<Class<?>, Optional<?>> TypeBeanCache = new HashMap<>();
    private static final Map<Class<?>, Optional<?>> SuitableTypeBeanCache = new HashMap<>();

    /**
     * 设置Spring上下文容器
     *
     * @param ctx Spring上下文容器
     */
    public static void setApplicationContext(ApplicationContext ctx) {
        applicationContext = ctx;
    }

    /**
     * 获取Spring上下文容器
     *
     * @return Spring上下文容器
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 根据名称获取Spring Bean
     *
     * @param beanName bean名称
     * @return bean实例
     */
    public static Object getBeanByName(String beanName) {
        Object bean = null;
        if (applicationContext != null && applicationContext.containsBean(beanName)) {
            bean = applicationContext.getBean(beanName);
        }
        return bean;
    }

    /**
     * 根据名称获取Spring Bean
     *
     * @param <T>
     * @param beanName bean名称
     * @param cls      bean类型
     * @return bean实例
     */
    public static <T> T getBeanByName(String beanName, Class<T> cls) {
        T bean = null;
        if (applicationContext != null && applicationContext.containsBean(beanName)) {
            bean = applicationContext.getBean(beanName, cls);
        }
        return bean;
    }

    /**
     * 获取环境信息
     *
     * @return 环境信息
     */
    public static Environment getEnvironment() {
        if (applicationContext != null) {
            return applicationContext.getEnvironment();
        }
        return null;
    }

    /**
     * 获取属性值
     *
     * @param key 属性名称（键）
     * @return 属性值
     */
    public static String getProperty(String key) {
        Environment env = getEnvironment();
        if (env != null) {
            return env.getProperty(key);
        }
        return null;
    }

    /**
     * 获取属性值
     *
     * @param key          属性名称（键）
     * @param defaultValue 默认值
     * @return
     */
    public static String getProperty(String key, String defaultValue) {
        Environment env = getEnvironment();
        if (env != null) {
            return env.getProperty(key, defaultValue);
        } else {
            return defaultValue;
        }
    }

    /**
     * 根据类型获取Spring Bean
     *
     * @param <T>
     * @param cls 类型
     * @return bean实例
     */
    public static <T> T getBeanByType(Class<T> cls) {
        Optional<T> obean = getOptionalTypeBean(cls);
        T bean = obean.get();
        return bean;
    }

    @SuppressWarnings("unchecked")
    private static <T> Optional<T> getOptionalTypeBean(Class<T> cls) {
        if (!TypeBeanCache.containsKey(cls)) {
            setOptionalTypeBean(cls);
        }
        return (Optional<T>) TypeBeanCache.get(cls);
    }

    private static void setOptionalTypeBean(Class<?> cls) {
        try {
            Object bean = applicationContext.getBean(cls);
            TypeBeanCache.put(cls, Optional.of(bean));
        } catch (Exception e) {
            log.error("", e);
        }
    }

    /**
     * 根据类型获取所有的Spring Bean
     *
     * @param <T>
     * @param cls 类型
     * @return 所有bean实例
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> cls) {
        Map<String, T> map = null;
        if (applicationContext != null) {
            map = applicationContext.getBeansOfType(cls);
        }
        return map;
    }

    /**
     * 获取最合适的Spring Bean
     *
     * @param <T>
     * @param cls 类型
     * @return
     */
    public static <T> T getBeanOfSuitable(Class<T> cls) {
        Optional<T> obean = getOptionalSuitableBean(cls);
        T bean = obean.get();
        return bean;
    }

    @SuppressWarnings("unchecked")
    private static <T> Optional<T> getOptionalSuitableBean(Class<T> cls) {
        if (!SuitableTypeBeanCache.containsKey(cls)) {
            setOptionalSuitableBean(cls);
        }
        return (Optional<T>) SuitableTypeBeanCache.get(cls);
    }

    private static void setOptionalSuitableBean(Class<?> cls) {
        try {
            Map<String, ?> map = applicationContext.getBeansOfType(cls);
            Object t = null;
            int priority = Integer.MIN_VALUE;
            if (map != null && map.size() > 0) {
                if (map.size() == 1) {
                    t = map.values().iterator().next();
                } else if (Priority.class.isAssignableFrom(cls)) {
                    for (Object bean : map.values()) {
                        Priority p = (Priority) bean;
                        if (p.getPriority() > priority) {
                            priority = p.getPriority();
                            t = bean;
                        }
                    }
                } else {
                    t = map.values().iterator().next();
                }
            }
            SuitableTypeBeanCache.put(cls, Optional.of(t));
        } catch (Exception e) {
            log.error("", e);
        }
    }

    /**
     * 获取对象映射工厂
     *
     * @return
     */
    public static ObjectMapper getObjectMapper() {
        ObjectMapper objectMapper = getBeanByType(ObjectMapper.class);
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
        }
        return objectMapper;
    }

    /**
     * 获取属性值
     *
     * @param <T>
     * @param key 属性名称（键）
     * @param cls 类型
     * @return 属性值
     */
    public static <T> T getProperty(String key, Class<T> cls) {
        Environment env = getEnvironment();
        if (env != null) {
            return env.getProperty(key, cls);
        }
        return null;
    }

    /**
     * 获取属性值
     *
     * @param <T>
     * @param key          属性名称（键）
     * @param cls          类型
     * @param defaultValue 默认值
     * @return 属性值
     */
    public static <T> T getProperty(String key, Class<T> cls, T defaultValue) {
        Environment env = getEnvironment();
        if (env != null) {
            return env.getProperty(key, cls);
        } else {
            return defaultValue;
        }
    }

    /**
     * 对象序列化为JSON字符串
     *
     * @param object 对象
     * @return JSON字符串
     */
    public static String serialize(Object object) {
        try {
            String content = getObjectMapper().writeValueAsString(object);
            return content;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }

    }

    /**
     * JSON字符串反序列化为对象
     *
     * @param <T>     对象泛型类型
     * @param content JSON字符串
     * @param cls     对象类型（不能是接口，需要使用实现类）
     * @return
     */
    public static <T> T deserialize(String content, Class<T> cls) {
        try {
            T value = getObjectMapper().readValue(content, cls);
            return value;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public static BeanDefinitionBuilder getBeanDefinitionBuilder() {
        return BeanDefinitionBuilder.genericBeanDefinition();
    }

    public static BeanDefinitionBuilder getBeanDefinitionBuilder(Class<?> cls) {
        return BeanDefinitionBuilder.genericBeanDefinition(cls);
    }

    public static BeanDefinitionBuilder getBeanDefinitionBuilder(String beanClassName) {
        return BeanDefinitionBuilder.genericBeanDefinition(beanClassName);
    }

    public static void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        if (applicationContext != null
                && applicationContext.getAutowireCapableBeanFactory() instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext
                    .getAutowireCapableBeanFactory();
            registry.registerBeanDefinition(beanName, beanDefinition);
        }
    }

    public static void registerSingleton(String beanName, Object singletonObject) {
        if (applicationContext != null
                && applicationContext.getAutowireCapableBeanFactory() instanceof DefaultListableBeanFactory) {
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
                    .getAutowireCapableBeanFactory();
            beanFactory.registerSingleton(beanName, singletonObject);
        }
    }

    public static void remove(String beanName) {
        Object bean = getBeanByName(beanName);
        if (applicationContext != null
                && applicationContext.getAutowireCapableBeanFactory() instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext
                    .getAutowireCapableBeanFactory();
            if (registry.containsBeanDefinition(beanName)) {
                registry.removeBeanDefinition(beanName);
            }
        }
        if (applicationContext != null
                && applicationContext.getAutowireCapableBeanFactory() instanceof DefaultListableBeanFactory) {
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
                    .getAutowireCapableBeanFactory();
            if (beanFactory.containsSingleton(beanName)) {
                beanFactory.destroySingleton(beanName);
            }
        }
        if (bean instanceof Closeable) {
            IoUtil.closeQuietly((Closeable) bean);
        }
        if (bean instanceof DisposableBean) {
            try {
                ((DisposableBean) bean).destroy();
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    public static <T> T build(Class<T> cls, Map<String, Object> properties, ConfigurationPropertyNameAliases aliases) {
        T result = BeanUtils.instantiateClass(cls);
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        Binder binder = null;
        if (aliases != null) {
            binder = new Binder(source.withAliases(aliases));
        } else {
            binder = new Binder(source);
        }
        binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
        return result;
    }

    /**
     * 发布事件
     *
     * @param event
     */
    public static void publishEvent(ApplicationEvent event) {
        if (applicationContext == null) {
            return;
        }
        applicationContext.publishEvent(event);
    }

    public static ContextHolder getGlobalContextHolder() {
        return globalContextHolder;
    }

    public static String[] getBeanNamesForAnnotation(Class c) {
        return applicationContext.getBeanNamesForAnnotation(c);
    }

    public static Class<?> getType(String beanName) {
        return applicationContext.getType(beanName);
    }
}